/*
 * Copyright (c) 1999, 2013, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package javax.management.loading;

import static com.sun.jmx.defaults.JmxProperties.MLET_LOGGER;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;

/**
 * This class is used for parsing URLs.
 *
 * @since 1.5
 */
class MLetParser {

/*
  * ------------------------------------------
  *   PRIVATE VARIABLES
  * ------------------------------------------
  */

  /**
   * The current character
   */
  private int c;

  /**
   * Tag to parse.
   */
  private static String tag = "mlet";


  /*
  * ------------------------------------------
  *   CONSTRUCTORS
  * ------------------------------------------
  */

  /**
   * Create an MLet parser object
   */
  public MLetParser() {
  }

    /*
     * ------------------------------------------
     *   PUBLIC METHODS
     * ------------------------------------------
     */

  /**
   * Scan spaces.
   */
  public void skipSpace(Reader in) throws IOException {
    while ((c >= 0) && ((c == ' ') || (c == '\t') || (c == '\n') || (c == '\r'))) {
      c = in.read();
    }
  }

  /**
   * Scan identifier
   */
  public String scanIdentifier(Reader in) throws IOException {
    StringBuilder buf = new StringBuilder();
    while (true) {
      if (((c >= 'a') && (c <= 'z')) ||
          ((c >= 'A') && (c <= 'Z')) ||
          ((c >= '0') && (c <= '9')) || (c == '_')) {
        buf.append((char) c);
        c = in.read();
      } else {
        return buf.toString();
      }
    }
  }

  /**
   * Scan tag
   */
  public Map<String, String> scanTag(Reader in) throws IOException {
    Map<String, String> atts = new HashMap<String, String>();
    skipSpace(in);
    while (c >= 0 && c != '>') {
      if (c == '<') {
        throw new IOException("Missing '>' in tag");
      }
      String att = scanIdentifier(in);
      String val = "";
      skipSpace(in);
      if (c == '=') {
        int quote = -1;
        c = in.read();
        skipSpace(in);
        if ((c == '\'') || (c == '\"')) {
          quote = c;
          c = in.read();
        }
        StringBuilder buf = new StringBuilder();
        while ((c > 0) &&
            (((quote < 0) && (c != ' ') && (c != '\t') &&
                (c != '\n') && (c != '\r') && (c != '>'))
                || ((quote >= 0) && (c != quote)))) {
          buf.append((char) c);
          c = in.read();
        }
        if (c == quote) {
          c = in.read();
        }
        skipSpace(in);
        val = buf.toString();
      }
      atts.put(att.toLowerCase(), val);
      skipSpace(in);
    }
    return atts;
  }

  /**
   * Scan an html file for {@literal <mlet>} tags.
   */
  public List<MLetContent> parse(URL url) throws IOException {
    String mth = "parse";
    // Warning Messages
    String requiresTypeWarning = "<arg type=... value=...> tag requires type parameter.";
    String requiresValueWarning = "<arg type=... value=...> tag requires value parameter.";
    String paramOutsideWarning = "<arg> tag outside <mlet> ... </mlet>.";
    String requiresCodeWarning = "<mlet> tag requires either code or object parameter.";
    String requiresJarsWarning = "<mlet> tag requires archive parameter.";

    URLConnection conn;

    conn = url.openConnection();
    Reader in = new BufferedReader(new InputStreamReader(conn.getInputStream(),
        "UTF-8"));

    // The original URL may have been redirected - this
    // sets it to whatever URL/codebase we ended up getting
    //
    url = conn.getURL();

    List<MLetContent> mlets = new ArrayList<MLetContent>();
    Map<String, String> atts = null;

    List<String> types = new ArrayList<String>();
    List<String> values = new ArrayList<String>();

    // debug("parse","*** Parsing " + url );
    while (true) {
      c = in.read();
      if (c == -1) {
        break;
      }
      if (c == '<') {
        c = in.read();
        if (c == '/') {
          c = in.read();
          String nm = scanIdentifier(in);
          if (c != '>') {
            throw new IOException("Missing '>' in tag");
          }
          if (nm.equalsIgnoreCase(tag)) {
            if (atts != null) {
              mlets.add(new MLetContent(url, atts, types, values));
            }
            atts = null;
            types = new ArrayList<String>();
            values = new ArrayList<String>();
          }
        } else {
          String nm = scanIdentifier(in);
          if (nm.equalsIgnoreCase("arg")) {
            Map<String, String> t = scanTag(in);
            String att = t.get("type");
            if (att == null) {
              MLET_LOGGER.logp(Level.FINER,
                  MLetParser.class.getName(),
                  mth, requiresTypeWarning);
              throw new IOException(requiresTypeWarning);
            } else {
              if (atts != null) {
                types.add(att);
              } else {
                MLET_LOGGER.logp(Level.FINER,
                    MLetParser.class.getName(),
                    mth, paramOutsideWarning);
                throw new IOException(paramOutsideWarning);
              }
            }
            String val = t.get("value");
            if (val == null) {
              MLET_LOGGER.logp(Level.FINER,
                  MLetParser.class.getName(),
                  mth, requiresValueWarning);
              throw new IOException(requiresValueWarning);
            } else {
              if (atts != null) {
                values.add(val);
              } else {
                MLET_LOGGER.logp(Level.FINER,
                    MLetParser.class.getName(),
                    mth, paramOutsideWarning);
                throw new IOException(paramOutsideWarning);
              }
            }
          } else {
            if (nm.equalsIgnoreCase(tag)) {
              atts = scanTag(in);
              if (atts.get("code") == null && atts.get("object") == null) {
                MLET_LOGGER.logp(Level.FINER,
                    MLetParser.class.getName(),
                    mth, requiresCodeWarning);
                throw new IOException(requiresCodeWarning);
              }
              if (atts.get("archive") == null) {
                MLET_LOGGER.logp(Level.FINER,
                    MLetParser.class.getName(),
                    mth, requiresJarsWarning);
                throw new IOException(requiresJarsWarning);
              }
            }
          }
        }
      }
    }
    in.close();
    return mlets;
  }

  /**
   * Parse the document pointed by the URL urlname
   */
  public List<MLetContent> parseURL(String urlname) throws IOException {
    // Parse the document
    //
    URL url;
    if (urlname.indexOf(':') <= 1) {
      String userDir = System.getProperty("user.dir");
      String prot;
      if (userDir.charAt(0) == '/' ||
          userDir.charAt(0) == File.separatorChar) {
        prot = "file:";
      } else {
        prot = "file:/";
      }
      url =
          new URL(prot + userDir.replace(File.separatorChar, '/') + "/");
      url = new URL(url, urlname);
    } else {
      url = new URL(urlname);
    }
    // Return list of parsed MLets
    //
    return parse(url);
  }

}
